Home›Pajak Kendaraan Kalbar
AKTIF0.1.0androidkotlinnextjsprismapostgrescoolifyfcm

Pajak Kendaraan Kalbar

Dua app Android (user + admin) + backend API untuk cek nominal pajak kendaraan: user kirim plat + no WA, admin input 4 nominal, user transfer & konfirmasi via WhatsApp.

JSON

Mockup Variant

💡 Ide & Konsep

User login Google (Credential Manager) -> input plat (prefix KB hardcoded) + nomor WhatsApp -> backend simpan request PENDING + push FCM ke admin. Admin app: antrian pending -> detail -> input 4 nominal (pajak, biaya cek, biaya admin, biaya kirim) -> kirim; total dihitung backend. User dapat push -> layar rincian + total -> tombol Bayar -> layar rekening (multi-bank, salin no rek) + tombol konfirmasi WhatsApp (wa.me prefilled) + upload bukti transfer -> admin verifikasi -> status PAID/STRUK_SENT + push. Desain: variant V1 Clean Minimal (putih + biru #0a7cff, Linear-style).

Ringkasan

Sistem cek pajak kendaraan (region Bantul, prefix plat KB) terdiri dari 3 bagian:

1. pajak-kb-api — backend Next.js (App Router) + Prisma + PostgreSQL + Firebase Cloud Messaging, deploy di Coolify.

2. pajak-kb-user — app Android (Kotlin) untuk pemilik kendaraan.

3. pajak-kb-admin — app Android (Kotlin) untuk admin yang mengisi nominal.

Alur inti: user kirim plat + no WA -> admin input 4 nominal -> user transfer -> upload bukti + konfirmasi WhatsApp -> admin verifikasi.

Arsitektur & Alur Data

Login

User app pakai Credential Manager (Google Sign-In) -> dapat ID token -> POST /api/auth/google { idToken, fcmToken } -> backend verifikasi via google-auth-library (audience = web client ID) -> upsert User -> balikan JWT HS256 (30 hari) + role.

Role ditentukan whitelist env ADMIN_EMAILS — dievaluasi ulang setiap login, bukan sign-up bebas.

Request pajak

User app: input plat (regex ^KB \d{1,4} [A-Z]{1,3}$, prefix KB fixed di UI) + nomor WhatsApp -> POST /api/tax-request { platNomor, whatsapp }.

Backend: normalisasi plat (KB1475WQ -> KB 1475 WQ) dan WA (08xx -> 62xx), rate limit 5 request/jam per user, simpan status PENDING, kirim FCM ke semua admin.

Respon admin

Admin app: GET /api/tax-request?scope=all&status=PENDING -> buka detail -> POST /api/tax-request/{id}/response { nominalPajak, biayaCek, biayaAdmin, biayaKirim }.

Backend: hanya role ADMIN, hanya kalau status PENDING, simpan 4 nominal + respondedAt + responseExpires (24 jam) + buat record Payment status WAITING_UPLOAD -> push FCM ke user.

Pembayaran

User app: GET /api/bank-accounts (multi-bank: BCA, Mandiri) -> tampil no rekening + tombol salin -> GET /api/payment/{id}/confirm-link balikan URL wa.me dengan pesan prefilled (plat, request id, total).

User upload bukti: POST /api/payment/{id}/upload (multipart, max 8MB, jpeg/png/webp/pdf) -> Payment jadi WAITING_VERIFY -> push ke admin.

Admin verifikasi: POST /api/payment/{id}/verify { sendStruk?: true } -> PAID atau STRUK_SENT -> push ke user.

Model Data (Prisma)

model User {
  id, googleSub @unique, email @unique, name?, role (USER|ADMIN),
  fcmToken?, whatsapp?, createdAt
}

model TaxRequest {
  id, platNomor, whatsapp, status (PENDING|RESPONDED|CANCELLED|EXPIRED),
  nominalPajak?, biayaCek?, biayaAdmin?, biayaKirim?,
  userId, adminId?, payment?,
  createdAt, respondedAt?, responseExpires?, updatedAt
}

model Payment {
  id, requestId @unique, status (WAITING_UPLOAD|WAITING_VERIFY|PAID|STRUK_SENT),
  proofUrl?, verifiedAt?, verifiedBy?
}

model BankAccount {
  id, bank, number, holder, active, order
}

model User {

id, googleSub @unique, email @unique, name?, role (USER|ADMIN),

fcmToken?, whatsapp?, createdAt

}

model TaxRequest {

id, platNomor, whatsapp, status (PENDING|RESPONDED|CANCELLED|EXPIRED),

nominalPajak?, biayaCek?, biayaAdmin?, biayaKirim?,

userId, adminId?, payment?,

createdAt, respondedAt?, responseExpires?, updatedAt

}

model Payment {

id, requestId @unique, status (WAITING_UPLOAD|WAITING_VERIFY|PAID|STRUK_SENT),

proofUrl?, verifiedAt?, verifiedBy?

}

model BankAccount {

id, bank, number, holder, active, order

}


Total tagihan = nominalPajak + biayaCek + biayaAdmin + biayaKirim (dihitung backend, bukan dikirim klien).

Total tagihan = nominalPajak + biayaCek + biayaAdmin + biayaKirim (dihitung backend, bukan dikirim klien).

API Endpoints

| Method | Path | Akses | Fungsi |

|---|---|---|---|

| POST | /api/auth/google | publik | Verifikasi Google ID token, upsert user, balikan JWT |

| GET | /api/me | auth | Profil user |

| PATCH | /api/me | auth | Update whatsapp / fcmToken |

| POST | /api/tax-request | auth | Buat request (plat + WA) |

| GET | /api/tax-request | auth | List request (?status=&scope=all khusus admin) |

| GET | /api/tax-request/{id} | auth | Detail (user: milik sendiri) |

| POST | /api/tax-request/{id}/response | admin | Input 4 nominal |

| POST | /api/tax-request/{id}/cancel | auth | Batalkan (hanya PENDING) |

| GET | /api/bank-accounts | auth | Daftar rekening aktif |

| GET | /api/payment/{id}/confirm-link | auth | URL wa.me prefilled |

| POST | /api/payment/{id}/upload | auth | Upload bukti transfer (multipart) |

| POST | /api/payment/{id}/verify | admin | Verifikasi pembayaran |

| GET | /api/uploads/{file} | publik | Serve file bukti (blok path traversal) |

| GET | /api/health | publik | Healthcheck Coolify |

Backend — Konfigurasi Env

DATABASE_URL=postgres://postgres:<PASS>@<container>:5432/postgres?schema=public
JWT_SECRET=<32+ char random>
GOOGLE_CLIENT_ID=<web client id>
ADMIN_EMAILS=andy@example.com,admin2@example.com
ADMIN_WA_NUMBER=62812xxxxxxx
FCM_SERVICE_ACCOUNT_B64=<base64 service account json>
APP_BASE_URL=https://...
UPLOAD_DIR=data/uploads

DATABASE_URL=postgres://postgres:<PASS>@<container>:5432/postgres?schema=public

JWT_SECRET=<32+ char random>

GOOGLE_CLIENT_ID=<web client id>

ADMIN_EMAILS=andy@example.com,admin2@example.com

ADMIN_WA_NUMBER=62812xxxxxxx

FCM_SERVICE_ACCOUNT_B64=<base64 service account json>

APP_BASE_URL=https://...

UPLOAD_DIR=data/uploads


Setup produksi: Coolify -> New resource dari GitHub `djvpri/pajak-kb-api` -> build pack Nixpacks -> tambah Postgres -> set env -> deploy -> jalankan `npm run db:migrate && npm run db:seed`.

Catatan: prisma harus v5 (`prisma@5` + `@prisma/client@5`); prisma 8 rc tidak punya command `generate` di CLI-nya.

Setup produksi: Coolify -> New resource dari GitHub djvpri/pajak-kb-api -> build pack Nixpacks -> tambah Postgres -> set env -> deploy -> jalankan npm run db:migrate && npm run db:seed.

Catatan: prisma harus v5 (prisma@5 + @prisma/client@5); prisma 8 rc tidak punya command generate di CLI-nya.

Status & State Machine

Request

PENDING -> (admin isi nominal) -> RESPONDED

PENDING -> (user/admin batal) -> CANCELLED

RESPONDED -> (lewat responseExpires 24 jam) -> EXPIRED

Payment

WAITING_UPLOAD -> (user upload bukti) -> WAITING_VERIFY

WAITING_VERIFY -> (admin verify) -> PAID

PAID -> (admin kirim struk) -> STRUK_SENT

Desain UI (V1 Clean Minimal)

Dipilih dari 3 variant mockup. Karakter: putih + biru #0a7cff, whitespace lebar, tipografi system font, kartu border tipis #e3e6ea, radius 10–12px.

User app — 5 layar

1. Login — judul "Cek Pajak Kendaraan" + tombol "Lanjutkan dengan Google".

2. Input — plat 3 kotak (KB fixed | angka | kode), field nomor WhatsApp dengan prefix +62 hijau, tombol "Kirim Permintaan".

3. Menunggu — tag status, plat, spinner, request ID, tombol Batalkan.

4. Rincian — total besar biru, 4 baris (pajak, biaya cek, biaya admin, biaya kirim), tombol Bayar.

5. Pembayaran — total, kartu rekening (bank, no rek + tombol Salin, atas nama), tombol hijau "Konfirmasi via WhatsApp", catatan upload bukti.

Admin app — 4 layar

1. Antrian — tab Pending/Selesai/Batal dengan badge, kartu request (plat, nama, no WA, waktu).

2. Detail — plat highlight, nama, no WA, waktu, tombol Input Nominal / Tolak.

3. Input Nominal — 4 field rupiah + total auto-sum, tombol Kirim ke User.

4. Konfirmasi — checkmark, status Selesai, rincian + total, tombol Lihat Antrian.

Langkah Berikutnya

1. Backend: push kode (selesai, 37 file di djvpri/pajak-kb-api), set env Coolify, jalankan migration + seed, deploy.

2. Google Cloud: buat OAuth client Android untuk com.pajakkb.user dan com.pajakkb.admin (package name + SHA-1), web client untuk verifikasi token backend.

3. Firebase: buat project / reuse, download service account JSON -> base64 -> FCM_SERVICE_ACCOUNT_B64; google-services.json untuk kedua app.

4. User app: Kotlin + Compose, Credential Manager, Retrofit, FCM, 5 layar sesuai desain.

5. Admin app: Kotlin + Compose, role guard (hanya ADMIN boleh masuk), 4 layar.

6. Belum dibuat: cron expire request lewat 24 jam (scripts/expire.mjs), rate limit pindah ke Redis kalau multi-instance, storage bukti pindah S3/R2 kalau traffic naik.